package com.ddwl.schema.util;

import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.*;
import java.util.*;


/**
 * @author Fu Zhaohui
 */
public final class DateUtil {

    public static final String DATE_FORMAT = "yyyy-MM-dd";
    public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DATE_TIME_FORMAT1 = "yyyyMMddHHmmss";
    public static final String DATE_TIME_FORMAT2 = "yyyyMMddHHmmssSSS";
    public static final String DATE_TIME_FORMAT3 = "yyyy-MM-dd HH:mm";
    public static final String DATE_TIME_FORMAT4 = "yyMMddHHmmssSSS";


    private static ThreadLocal<Map<String, DateTimeFormatter>> sThreadLocal = new ThreadLocal<>();

    private static DateTimeFormatter getFormat(String pattern) {
        Map<String, DateTimeFormatter> strDateFormatMap = sThreadLocal.get();

        if (strDateFormatMap == null) {
            strDateFormatMap = new HashMap<>(8);
            sThreadLocal.set(strDateFormatMap);
        }

        DateTimeFormatter simpleDateFormat = strDateFormatMap.get(pattern);
        if (simpleDateFormat == null) {
            simpleDateFormat = DateTimeFormatter.ofPattern(pattern, Locale.getDefault());
            strDateFormatMap.put(pattern, simpleDateFormat);
        }

        return simpleDateFormat;
    }


    public static Instant parseDate(String dateStr) {
        return LocalDateTime.parse(dateStr, getFormat(DATE_TIME_FORMAT)).atZone(ZoneId.systemDefault()).toInstant();
    }

    public static Instant parseDate(String dateStr, String format) {
        return LocalDateTime.parse(dateStr, getFormat(format)).atZone(ZoneId.systemDefault()).toInstant();
    }

    public static String formatDate(Instant date) {
        LocalDateTime cal = LocalDateTime.ofInstant(date, ZoneId.systemDefault());
        return getFormat(DATE_FORMAT).format(cal);
    }

    public static String formatDateTime(Instant date) {
        LocalDateTime cal = LocalDateTime.ofInstant(date, ZoneId.systemDefault());
        return getFormat(DATE_TIME_FORMAT).format(cal);
    }

    public static String formatDate(Instant date, String format) {
        LocalDateTime cal = LocalDateTime.ofInstant(date, ZoneId.systemDefault());
        if (format == null) {
            format = DATE_TIME_FORMAT;
        }
        return getFormat(format).format(cal);
    }

    public static final String getCurrDate(String format) {
        return getFormat(format).format(LocalDateTime.now());
    }

    public static Instant addDay(Instant date, int days) {
        return date.plus(days, ChronoUnit.DAYS);
    }

    public static Instant addMonth(Instant date, int months) {
        return date.plus(months, ChronoUnit.MONTHS);
    }

    public static Instant addYear(Instant date, int years) {
        return date.plus(years, ChronoUnit.YEARS);
    }

    public static Instant addMinutes(Instant date, int minutes) {
        return date.plus(minutes, ChronoUnit.MINUTES);
    }

    public static Instant addHours(Instant date,int hours){
        return date.plus(hours, ChronoUnit.HOURS);
    }

    public static long diffDays(Instant beginDate, Instant endDate) {
        return Duration.between(beginDate, endDate).toDays();
    }

    public static long diffHours(Instant beginDate, Instant endDate) {
        return Duration.between(beginDate, endDate).toHours();
    }

    public static Instant getBeforeWeek(Instant date) {
        return date.plus(-1, ChronoUnit.WEEKS)
                .with(ChronoField.HOUR_OF_DAY, 0)
                .with(ChronoField.MINUTE_OF_HOUR, 0)
                .with(ChronoField.SECOND_OF_MINUTE, 0);
    }

    /**
     * 获取这周第一天
     *
     * @param date
     * @return
     */
    public static Instant getFirstDayWeek(Instant date) {
        return date.with(WeekFields.of(Locale.CHINA).dayOfWeek(), 1)
                .with(ChronoField.HOUR_OF_DAY, 0)
                .with(ChronoField.MINUTE_OF_HOUR, 0)
                .with(ChronoField.SECOND_OF_MINUTE, 0);
    }

    /**
     * 获取这周第七天
     *
     * @param date
     * @return
     */
    public static Instant getLastdayWeek(Instant date) {
        return date.with(WeekFields.of(Locale.CHINA).dayOfWeek(), 7)
                .with(ChronoField.HOUR_OF_DAY, 0)
                .with(ChronoField.MINUTE_OF_HOUR, 0)
                .with(ChronoField.SECOND_OF_MINUTE, 0);
    }

    public static Instant getLastestWeek(Instant date) {
        return date.plus(1, ChronoUnit.DAYS)
                .with(ChronoField.HOUR_OF_DAY, 0)
                .with(ChronoField.MINUTE_OF_HOUR, 0)
                .with(ChronoField.SECOND_OF_MINUTE, 0);
    }

    public static Instant getTomorrow(Instant date) {
        return date.plus(1, ChronoUnit.DAYS)
                .with(ChronoField.HOUR_OF_DAY, 0)
                .with(ChronoField.MINUTE_OF_HOUR, 0)
                .with(ChronoField.SECOND_OF_MINUTE, 0);
    }

    public static Instant getYesterday(Instant date) {
        return date
                .plus(-1, ChronoUnit.DAYS)
                .with(ChronoField.HOUR_OF_DAY, 23)
                .with(ChronoField.MINUTE_OF_HOUR, 59)
                .with(ChronoField.SECOND_OF_MINUTE, 59);
    }

    /**
     *java获取日期本周一时间0点时间
     */
    public static Instant getMondayOfThisWeek() {
        Calendar cal = Calendar.getInstance();
        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONDAY), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
        int d = 0;
        if (cal.get(Calendar.DAY_OF_WEEK) == 1) {
            d = -6;
        } else {
            d = 2 - cal.get(Calendar.DAY_OF_WEEK);
        }
        cal.add(Calendar.DAY_OF_WEEK, d);
        return cal.getTime().toInstant();
    }


    /**
     *获得当天0点时间
     */
    public static Instant getTimesMorning() {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime().toInstant();
    }

    /**
     * 获取今天的剩余时间
     * @return
     */
    public static Integer getToDayRemainingSeconds(){
        LocalDateTime midnight = LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
        Integer seconds = (int) ChronoUnit.SECONDS.between(LocalDateTime.now(), midnight);
        return seconds;
    }

    /**
     *获得当天24点时间
     */
    public static Instant getTimesNight() {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.HOUR_OF_DAY, 23);
        cal.set(Calendar.SECOND, 59);
        cal.set(Calendar.MINUTE, 59);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime().toInstant();
    }

    /**
     *获得距离今天某一天的0点时间
     * @param date 1表示距离今天1天,即明天
     */
    public static Instant getTimesMorning(int date) {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.MILLISECOND, 0);
        cal.add(Calendar.DATE,date);
        return cal.getTime().toInstant();
    }
    /**
     *获得距离今天某一天的24点时间
     * @param date 1表示距离今天1天,即明天
     */
    public static Instant getTimesNight(int date) {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.HOUR_OF_DAY,23);
        cal.set(Calendar.SECOND, 59);
        cal.set(Calendar.MINUTE, 59);
        cal.set(Calendar.MILLISECOND, 0);
        cal.add(Calendar.DATE,date);
        return cal.getTime().toInstant();
    }

    /**
     *获得距离某一天的0点时间
     * @param date 1表示距离今天1天,即第二天
     */
    public static Instant getTimesMorning(Instant instant,int date) {
        Calendar cal = GregorianCalendar.from(ZonedDateTime.ofInstant(instant, ZoneId.systemDefault()));
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.MILLISECOND, 0);
        cal.add(Calendar.DATE,date);
        return cal.getTime().toInstant();
    }

    /**
     *获得距离某一天的24点时间
     * @param date 1表示距离今天1天,即第二天
     */
    public static Instant getTimesNight(Instant instant,int date) {
        Calendar cal = GregorianCalendar.from(ZonedDateTime.ofInstant(instant, ZoneId.systemDefault()));
        cal.set(Calendar.HOUR_OF_DAY, 23);
        cal.set(Calendar.SECOND, 59);
        cal.set(Calendar.MINUTE, 59);
        cal.set(Calendar.MILLISECOND, 0);
        cal.add(Calendar.DATE,date);
        return cal.getTime().toInstant();
    }



    /**
     * 获取上一个月的第一天
     * @return
     */
    public static Instant getBeforeMonthStart() {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MONTH, -1);
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMinimum(Calendar.DAY_OF_MONTH));
        return calendar.getTime().toInstant();
    }

    /**
     * 获取上一个月的最后一天
     * @return
     */
    public static Instant getBeforeMonthEnd() {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MONTH, -1);
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        return calendar.getTime().toInstant();
    }

    /**
     * 获取下一个月的第一天
     * @param date
     * @return
     */
    public static Instant getFirstDayAfterMonth(Instant date) {
        LocalDate localDate = LocalDateTime.ofInstant(date,ZoneId.systemDefault()).toLocalDate();
        LocalDateTime minusDate = localDate.minusMonths(-1).with(TemporalAdjusters.firstDayOfMonth()).atTime(0,0,0);
        return minusDate.atZone(ZoneId.systemDefault()).toInstant();
    }

    /**
     * 获取当前月的第一天
     * @param date
     * @return
     */
    public static Instant getFirstDayMonth(Instant date) {
        LocalDate localDate = LocalDateTime.ofInstant(date,ZoneId.systemDefault()).toLocalDate();
        LocalDateTime minusDate = localDate.with(TemporalAdjusters.firstDayOfMonth()).atTime(0,0,0);
        return minusDate.atZone(ZoneId.systemDefault()).toInstant();
    }

    public static Instant getLastDayOfMonth(Instant date) {
        return date.with(TemporalAdjusters.firstDayOfNextMonth())
                .with(ChronoField.HOUR_OF_DAY, 0)
                .with(ChronoField.MINUTE_OF_HOUR, 0)
                .with(ChronoField.SECOND_OF_MINUTE, 0);
    }

    public static Instant getFirstDayOfBeforeThreeMonth(Instant date) {
        return date.with(TemporalAdjusters.firstDayOfMonth())
                .plus(-2, ChronoUnit.MONTHS)
                .with(ChronoField.HOUR_OF_DAY, 0)
                .with(ChronoField.MINUTE_OF_HOUR, 0)
                .with(ChronoField.SECOND_OF_MINUTE, 0);
    }

    public static Instant getFirstDayOfBeforeSixMonth(Instant date) {
        return date.with(TemporalAdjusters.firstDayOfMonth())
                .plus(-5, ChronoUnit.MONTHS)
                .with(ChronoField.HOUR_OF_DAY, 0)
                .with(ChronoField.MINUTE_OF_HOUR, 0)
                .with(ChronoField.SECOND_OF_MINUTE, 0);
    }

    public static Instant getFirstDayOfYear(Instant date) {
        return date.with(TemporalAdjusters.firstDayOfYear())
                .with(ChronoField.HOUR_OF_DAY, 0)
                .with(ChronoField.MINUTE_OF_HOUR, 0)
                .with(ChronoField.SECOND_OF_MINUTE, 0);
    }
    public static String getDayWeek(Instant date) {
        String[] dayNames = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
        LocalDateTime cal = LocalDateTime.ofInstant(date, ZoneId.systemDefault());
        TemporalField fieldISO = WeekFields.of(Locale.CHINA).dayOfWeek();
        int dayOfWeek = cal.get(fieldISO);
        if (dayOfWeek < 0) {
            dayOfWeek = 0;
        }
        return dayNames[dayOfWeek];
    }

    public static String getDayOfWeek(Instant date) {
        String[] dayNames = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
        Calendar cal = GregorianCalendar.from(ZonedDateTime.ofInstant(date, ZoneId.systemDefault()));
        int dayOfWeek=cal.get(Calendar.DAY_OF_WEEK)-1;
        return dayNames[dayOfWeek];
    }

    public static Instant getFirstDayOfNextMonth(Instant date) {
        return date.with(TemporalAdjusters.firstDayOfNextMonth())
                .with(ChronoField.HOUR_OF_DAY, 0)
                .with(ChronoField.MINUTE_OF_HOUR, 0)
                .with(ChronoField.SECOND_OF_MINUTE, 0);
    }

    public static Date morningFourHour() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, 4);
        calendar.add(Calendar.DAY_OF_MONTH, 1);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        return calendar.getTime();
    }

    public static Instant getTimeAfterSomeMinute(int minute) {
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.MINUTE, minute);
        return cal.getTime().toInstant();
    }

    public static Instant getSpecifiedTime(Instant instant,int minute) {
        Calendar cal = GregorianCalendar.from(ZonedDateTime.ofInstant(instant, ZoneId.systemDefault()));
        cal.add(Calendar.MINUTE, minute);
        return cal.getTime().toInstant();
    }

    public static List<String> currentWeek() {
        List<String> list = new ArrayList<>();
        Instant monday=getMondayOfThisWeek();
        list.add(formatDate(monday,DATE_FORMAT));
        for (int i = 1; i < 7; i++) {
            list.add(formatDate(addDay(monday, i),DATE_FORMAT));
        }
        return list;
    }

    public static Instant getMondayOfWeek(Instant date) {
        Calendar cal = GregorianCalendar.from(ZonedDateTime.ofInstant(date, ZoneId.systemDefault()));
        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONDAY), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
        int d = 0;
        if (cal.get(Calendar.DAY_OF_WEEK) == 1) {
            d = -6;
        } else {
            d = 2 - cal.get(Calendar.DAY_OF_WEEK);
        }
        cal.add(Calendar.DAY_OF_WEEK, d);
        return cal.getTime().toInstant();
    }

    public static Instant stringToInstant(String string) {
        SimpleDateFormat formatter = new SimpleDateFormat(DATE_FORMAT);
        ParsePosition pos = new ParsePosition(0);
        Date date = formatter.parse(string, pos);
        return date.toInstant();
    }

}
